package com.echo.code.thread.atomic;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * ABAProblem Created by baiyap on 2018/10/10.
 */
public class ABAProblem {

  public static void main(String[] args) throws InterruptedException {
    Stack<String> stack = new Stack<>();
    stack.push("B");
    stack.push("A");
    System.out.println("Stack init:" + stack);

    Thread thread01 = new Thread(() -> {
      stack.pop();
      System.out.println("Thread1 pop :" + stack);
    });
    thread01.setName("Thread1");
    thread01.start();

    Thread thread02 = new Thread(() -> {
      Node<String> A = stack.pop();
      System.out.println("Thread2 pop A:" + stack);
      stack.pop();
      System.out.println("Thread2 pop B:" + stack);
      stack.push("D");
      System.out.println("Thread2 push D:" + stack);
      stack.push("C");
      System.out.println("Thread2 push C:" + stack);
      stack.push(A);
      System.out.println("Thread2 push A:" + stack);
    });
    thread02.setName("Thread2");
    thread02.start();

    thread01.join();
    thread02.join();
    System.out.println("Stack result:" + stack);
  }

  private static class Stack<T> {

    // initialStamp = 0
    AtomicStampedReference<Node<T>> head = new AtomicStampedReference<>(null, 0);


    public void push(T value) {
      Node<T> node = new Node<>(value);
      push(node);
    }

    public void push(Node<T> node) {
      for (; ; ) {
        Node<T> tmpHead = head.getReference();
        int stamp = head.getStamp();
        if (head.compareAndSet(tmpHead, node, stamp, stamp + 1)) {
          node.setNext(tmpHead);
          return;
        }
      }
    }

    public Node<T> pop() {
      for (; ; ) {
        Node<T> node = head.getReference();
        int stamp = head.getStamp();
        if (node == null) {
          return null;
        }
        Node<T> nextNode = node.getNext();
        // add this sleep to cause ABA problem
        sleep();
        if (head.compareAndSet(node, nextNode, stamp, stamp + 1)) {
          return node;
        }
      }
    }

    private void sleep() {
      if (Thread.currentThread().getName().equals("Thread1")) {
        try {
          TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
        }
      }
    }

    @Override
    public String toString() {
      StringBuilder sb = new StringBuilder("[");
      Node<T> node = head.getReference();
      while (node != null) {
        sb.append(node.getValue());
        if (node.getNext() != null) {
          sb.append(",");
        }
        node = node.getNext();
      }
      sb.append("]");
      return sb.toString();
    }
  }

  private static class Node<T> {

    private T value;
    private Node<T> next;

    public Node(T value) {
      this.value = value;
    }

    public Node<T> getNext() {
      return next;
    }

    public void setNext(Node<T> next) {
      this.next = next;
    }

    public T getValue() {
      return value;
    }

    public void setValue(T value) {
      this.value = value;
    }
  }
}
